/*-------------------------------------------------------------------------
 *
 * varsup.c
 *	  postgres OID 和 XID 变量支持例程
 *
 * Copyright (c) 2000-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/access/transam/varsup.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/clog.h"
#include "access/commit_ts.h"
#include "access/subtrans.h"
#include "access/transam.h"
#include "access/xact.h"
#include "access/xlogutils.h"
#include "commands/dbcommands.h"
#include "miscadmin.h"
#include "postmaster/autovacuum.h"
#include "storage/pmsignal.h"
#include "storage/proc.h"
#include "utils/syscache.h"


/* 每次 XLOG 写入要预取（预分配）的 OID 数量 */
#define VAR_OID_PREFETCH		8192

/* 指向共享内存中的“变量缓存”的指针（由 shmem.c 设置） */
VariableCache ShmemVariableCache = NULL;


/*
 * 为新事务或子事务分配下一个 FullTransactionId。
 *
 * 新的 XID 也会在返回之前存储到 MyProc->xid/ProcGlobal->xids[] 中。
 *
 * 注意：当调用这个时，我们实际上已经在有效的事务中，因为 XID 现在不会在事务做任何事情之前被分配。所以如果我们想发出关于 XID 包装的警告，进行数据库查找是安全的。
 */
FullTransactionId GetNewTransactionId(bool fc_isSubXact)
{
	FullTransactionId fc_full_xid;
	TransactionId fc_xid;

	/*
	 * 工作进程在每个并行操作开始时同步事务状态，因此我们无法在此之后计算新的 XID。
	 */
	if (IsInParallelMode())
		elog(ERROR, "cannot assign TransactionIds during a parallel operation");

	/*
	 * 在启动初始化期间，我们返回特殊的启动事务 id。
	 */
	if (IsBootstrapProcessingMode())
	{
		Assert(!fc_isSubXact);
		MyProc->xid = BootstrapTransactionId;
		ProcGlobal->xids[MyProc->pgxactoff] = BootstrapTransactionId;
		return FullTransactionIdFromEpochAndXid(0, BootstrapTransactionId);
	}

	/* 安全检查，我们在 HS 备用中永远不应该走这么远 */
	if (RecoveryInProgress())
		elog(ERROR, "cannot assign TransactionIds during recovery");

	LWLockAcquire(XidGenLock, LW_EXCLUSIVE);

	fc_full_xid = ShmemVariableCache->nextXid;
	fc_xid = XidFromFullTransactionId(fc_full_xid);

	/*----------
	 * 检查是否可以安全地分配另一个 XID。这可以防止因 XID 包装而导致的灾难性数据丢失。基本规则是：
	 *
	 * 如果我们超过了 xidVacLimit，就开始强制进行自动真空周期。
	 * 如果我们超过了 xidWarnLimit，就开始发出警告。
	 * 如果我们超过了 xidStopLimit，拒绝执行事务，除非我们在单用户模式下运行（这为 DBA 提供了一条逃生通道，他以某种方式突破了之前的防御）。
	 *
	 * 注意，这段代码也出现在 GetNewMultiXactId 中。
	 *----------
	 */
	if (TransactionIdFollowsOrEquals(fc_xid, ShmemVariableCache->xidVacLimit))
	{
		/*
		 * 为了安全起见，在发送信号、警告等时释放 XidGenLock。这并不是因为我们在这种情况下关心保持并发，而是为了避免在执行 get_database_name() 时出现死锁的可能性。首先，复制我们在这个路径中需要的所有共享值。
		 */
		TransactionId fc_xidWarnLimit = ShmemVariableCache->xidWarnLimit;
		TransactionId fc_xidStopLimit = ShmemVariableCache->xidStopLimit;
		TransactionId fc_xidWrapLimit = ShmemVariableCache->xidWrapLimit;
		Oid			fc_oldest_datoid = ShmemVariableCache->oldestXidDB;

		LWLockRelease(XidGenLock);

		/*
		 * 为了避免信号淹没进程主控，我们每 64K 次事务启动只发出一次自动真空请求。这仍然在真正麻烦之前提供了很多机会。
		 */
		if (IsUnderPostmaster && (fc_xid % 65536) == 0)
			SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);

		if (IsUnderPostmaster &&
			TransactionIdFollowsOrEquals(fc_xid, fc_xidStopLimit))
		{
			char	   *fc_oldest_datname = get_database_name(fc_oldest_datoid);

			/* 即使该数据库已经消失也要抱怨 */
			if (fc_oldest_datname)
				ereport(ERROR,
						(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
						 errmsg("database is not accepting commands to avoid wraparound data loss in database \"%s\"",
								fc_oldest_datname),
						 errhint("Stop the postmaster and vacuum that database in single-user mode.\n"
								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
			else
				ereport(ERROR,
						(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
						 errmsg("database is not accepting commands to avoid wraparound data loss in database with OID %u",
								fc_oldest_datoid),
						 errhint("Stop the postmaster and vacuum that database in single-user mode.\n"
								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
		}
		else if (TransactionIdFollowsOrEquals(fc_xid, fc_xidWarnLimit))
		{
			char	   *fc_oldest_datname = get_database_name(fc_oldest_datoid);

			/* 即使该数据库已经消失也要抱怨 */
			if (fc_oldest_datname)
				ereport(WARNING,
						(errmsg("database \"%s\" must be vacuumed within %u transactions",
								fc_oldest_datname,
								fc_xidWrapLimit - fc_xid),
						 errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
			else
				ereport(WARNING,
						(errmsg("database with OID %u must be vacuumed within %u transactions",
								fc_oldest_datoid,
								fc_xidWrapLimit - fc_xid),
						 errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
								 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
		}

		/* 重新获取锁并重新开始 */
		LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
		fc_full_xid = ShmemVariableCache->nextXid;
		fc_xid = XidFromFullTransactionId(fc_full_xid);
	}

	/*
	 * 如果我们正在为提交日志的新页面分配第一个 XID，在返回之前将该提交日志页面清零。我们必须在持有 XidGenLock 时做到这一点，否则另一个事务可能会在我们清零页面之前获取并提交一个较晚的 XID。幸运的是，提交日志的一页可以容纳 32K 或更多的事务，因此我们不需要经常这样做。
	 *
	 * 还要扩展 pg_subtrans 和 pg_commit_ts。
	 */
	ExtendCLOG(fc_xid);
	ExtendCommitTs(fc_xid);
	ExtendSUBTRANS(fc_xid);

	/*
	 * 现在推进 nextXid 计数器。在我们成功完成 ExtendCLOG() 之后，这不能发生——如果该例程失败，我们希望下一个即将到来的事务再试一次。我们不能在没有 CLOG 空间的情况下分配更多的 XID。
	 */
	FullTransactionIdAdvance(&ShmemVariableCache->nextXid);

	/*
	 * 我们必须在释放 XidGenLock 之前将新的 XID 存储到共享的 ProcArray 中。这样可以确保每个活跃的 XID 都在 latestCompletedXid 之前存在于 ProcArray 中，这对正确跟踪 OldestXmin 是至关重要的；请参见 src/backend/access/transam/README。
	 *
	 * 请注意，ProcGlobal->xids/PGPROC->xid 的读取者应小心每个进程只获取一次值，而不是假设他们可以多次读取同一个值并每次都得到相同的答案。请注意，我们假设 TransactionId 和 int 的获取/存储是原子的。
	 *
	 * 同样的评论适用于 subxact xid 计数和溢出字段。
	 *
	 * 使用写屏障可以防止危险的代码重排；否则其他后端可能会并发地检查我的 subxids 信息，而我们不希望他们看到无效的中间状态，比如在数组条目填充之前增量的 nxids。
	 *
	 * 读取 nxids 的其他进程应该在读取 xids 元素之前，先调用 pg_read_barrier()，以确保他们不会读取未初始化的数组元素；请参见 src/backend/storage/lmgr/README.barrier。
	 *
	 * 如果 PGPROC 中没有空间放入子事务 XID，则设置缓存溢出标志。这会强迫读取者在 pg_subtrans 中查找，将子事务 XID 映射到顶级 XID。存在一个竞争条件窗口，因为新的 XID 在其父链接放入 pg_subtrans 之前不会显示为正在运行。然而，在任何人可能询问 XID 状态之前，这中间状态就会发生，因此这似乎是可以的。（在此窗口期间拍摄的快照 *将* 包含父 XID，因此当有人确实有理由询问时，它们会提供正确的答案。）
	 */
	if (!fc_isSubXact)
	{
		Assert(ProcGlobal->subxidStates[MyProc->pgxactoff].count == 0);
		Assert(!ProcGlobal->subxidStates[MyProc->pgxactoff].overflowed);
		Assert(MyProc->subxidStatus.count == 0);
		Assert(!MyProc->subxidStatus.overflowed);

		/* LWLockRelease 作为屏障 */
		MyProc->xid = fc_xid;
		ProcGlobal->xids[MyProc->pgxactoff] = fc_xid;
	}
	else
	{
		XidCacheStatus *fc_substat = &ProcGlobal->subxidStates[MyProc->pgxactoff];
		int			fc_nxids = MyProc->subxidStatus.count;

		Assert(fc_substat->count == MyProc->subxidStatus.count);
		Assert(fc_substat->overflowed == MyProc->subxidStatus.overflowed);

		if (fc_nxids < PGPROC_MAX_CACHED_SUBXIDS)
		{
			MyProc->subxids.xids[fc_nxids] = fc_xid;
			pg_write_barrier();
			MyProc->subxidStatus.count = fc_substat->count = fc_nxids + 1;
		}
		else
			MyProc->subxidStatus.overflowed = fc_substat->overflowed = true;
	}

	LWLockRelease(XidGenLock);

	return fc_full_xid;
}

/*
 * 读取 nextXid，但不分配它。
 */
FullTransactionId ReadNextFullTransactionId(void)
{
	FullTransactionId fc_fullXid;

	LWLockAcquire(XidGenLock, LW_SHARED);
	fc_fullXid = ShmemVariableCache->nextXid;
	LWLockRelease(XidGenLock);

	return fc_fullXid;
}

/*
 * 将 nextXid 向前推进到给定 xid 之后的值。纪元是推断的。
 * 这只能在恢复期间或从两阶段启动代码中调用。
 */
void AdvanceNextFullTransactionIdPastXid(TransactionId fc_xid)
{
	FullTransactionId fc_newNextFullXid;
	TransactionId fc_next_xid;
	uint32		fc_epoch;

	/*
	 * 在没有锁的情况下读取 nextXid 是安全的，因为这仅从启动进程或单进程模式调用，意味着没有其他进程可以修改它。
	 */
	Assert(AmStartupProcess() || !IsUnderPostmaster);

	/* 如果这不是足够高的 xid 来移动针，快速返回。 */
	fc_next_xid = XidFromFullTransactionId(ShmemVariableCache->nextXid);
	if (!TransactionIdFollowsOrEquals(fc_xid, fc_next_xid))
		return;

	/*
	 * 计算给定 xid 之后的 FullTransactionId。为此，我们保留现有的纪元，但检测何时我们已包装到新的纪元。这是必要的，因为当前 WAL 记录和 2PC 状态包含 32 位 xid。在这些情况下，包装逻辑是安全的，因为活动 xid 的跨度在 WAL 流中的任何给定时刻都不会超过一个纪元。
	 */
	TransactionIdAdvance(fc_xid);
	fc_epoch = EpochFromFullTransactionId(ShmemVariableCache->nextXid);
	if (unlikely(fc_xid < fc_next_xid))
		++fc_epoch;
	fc_newNextFullXid = FullTransactionIdFromEpochAndXid(fc_epoch, fc_xid);

	/*
	 * 当存在并发读取者时，我们仍然需要加锁以修改值。
	 */
	LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
	ShmemVariableCache->nextXid = fc_newNextFullXid;
	LWLockRelease(XidGenLock);
}

/*
 * 提升集群范围内最旧有效的 clog 条目的值。
 *
 * 我们必须获得 XactTruncationLock 来提升 oldestClogXid。在实际的 clog 截断期间并不需要持有锁，只在我们提升限制时需要，因为查找任意 xids 的代码需要持有 XactTruncationLock，从测试 oldestClogXid 到完成 clog 查找期间。
 */
void AdvanceOldestClogXid(TransactionId fc_oldest_datfrozenxid)
{
	LWLockAcquire(XactTruncationLock, LW_EXCLUSIVE);
	if (TransactionIdPrecedes(ShmemVariableCache->oldestClogXid,
							  fc_oldest_datfrozenxid))
	{
		ShmemVariableCache->oldestClogXid = fc_oldest_datfrozenxid;
	}
	LWLockRelease(XactTruncationLock);
}

/*
 * 使用当前最旧的 datfrozenxid（即，可能存在于我们集群中任何数据库中的最旧 XID）和具有该值的数据库的 OID 来确定最后一个安全的 XID 进行分配。
 */
void SetTransactionIdLimit(TransactionId fc_oldest_datfrozenxid, Oid fc_oldest_datoid)
{
	TransactionId fc_xidVacLimit;
	TransactionId fc_xidWarnLimit;
	TransactionId fc_xidStopLimit;
	TransactionId fc_xidWrapLimit;
	TransactionId fc_curXid;

	Assert(TransactionIdIsNormal(fc_oldest_datfrozenxid));

	/*
	 * 我们实际上陷入深层麻烦的地方是从最旧的潜在存在的 XID 走到一半。 （这个计算可能相差一个或两个计数，因为特殊的 XID 稍微减少了循环的大小。 但我们下面会留出足够的余量，所以这无关紧要。）
	 */
	fc_xidWrapLimit = fc_oldest_datfrozenxid + (MaxTransactionId >> 1);
	if (fc_xidWrapLimit < FirstNormalTransactionId)
		fc_xidWrapLimit += FirstNormalTransactionId;

	/*
	 * 一旦我们距离数据丢失不足 300 万事务，我们将拒绝继续在交互模式下分配 XID。 这为 DBA 在独立后端中修复问题留出了很多空间，同时与总 XID 空间相比并不算重要。 （如果 VACUUM 在 wal_level!=minimal 下截断，它需要一个 XID。 DBA 可能会出于本能执行的 "VACUUM (ANALYZE)" 会分配一个 XID。 因此，我们最好确保剩下很多 XID...）此外，在默认的 BLCKSZ 下，这留下了两个完全闲置的段。 在涉及页面或段算术的边缘案例 bug 发生时，闲置段会使这些 bug 在单用户模式之外无法到达。
	 */
	fc_xidStopLimit = fc_xidWrapLimit - 3000000;
	if (fc_xidStopLimit < FirstNormalTransactionId)
		fc_xidStopLimit -= FirstNormalTransactionId;

	/*
	 * 当我们距离数据丢失不足 4000 万事务时，我们将开始大声抱怨。 这有点任意，但如果你让燃油表降到满油的 2%，你会寻找下一个加油站吗？ 我们需要对此数字相对宽松，因为有很多场景下大部分事务都是由不会注意警告的自动客户端完成的。 （不，我们不会让这个可配置。 如果你知道足够多去配置它，你就足够知道不要先陷入这种麻烦。）
	 */
	fc_xidWarnLimit = fc_xidWrapLimit - 40000000;
	if (fc_xidWarnLimit < FirstNormalTransactionId)
		fc_xidWarnLimit -= FirstNormalTransactionId;

	/*
	 * 当 oldest_datfrozenxid 超过 autovacuum_freeze_max_age 事务时，我们将开始尝试强制启动自动清理。
	 *
	 * 注意：guc.c 确保 autovacuum_freeze_max_age 在一个合理的范围内，
	 * 以便 xidVacLimit 会在 xidWarnLimit 之前很久。
	 *
	 * 注意：autovacuum_freeze_max_age 是一个 PGC_POSTMASTER 参数，
	 * 因此我们不必担心处理该值的动态变化。
	 * 从 GUC 分配钩子中更新共享状态似乎并不现实（太多进程会尝试执行该钩子，
	 * 从而导致竞争条件以及与共享内存未连接的进程崩溃）。也许未来可以改进这一点。
	 * 另见 SetMultiXactIdLimit。
	 */
	fc_xidVacLimit = fc_oldest_datfrozenxid + autovacuum_freeze_max_age;
	if (fc_xidVacLimit < FirstNormalTransactionId)
		fc_xidVacLimit += FirstNormalTransactionId;

	/* 为新的限制值设置锁定足够长的时间 */
	LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
	ShmemVariableCache->oldestXid = fc_oldest_datfrozenxid;
	ShmemVariableCache->xidVacLimit = fc_xidVacLimit;
	ShmemVariableCache->xidWarnLimit = fc_xidWarnLimit;
	ShmemVariableCache->xidStopLimit = fc_xidStopLimit;
	ShmemVariableCache->xidWrapLimit = fc_xidWrapLimit;
	ShmemVariableCache->oldestXidDB = fc_oldest_datoid;
	fc_curXid = XidFromFullTransactionId(ShmemVariableCache->nextXid);
	LWLockRelease(XidGenLock);

	/* 记录信息 */
	ereport(DEBUG1,
			(errmsg_internal("transaction ID wrap limit is %u, limited by database with OID %u",
							 fc_xidWrapLimit, fc_oldest_datoid)));

	/*
	 * 如果超过自动清理的强制点，立即发出自动清理请求。其原因是自动清理一次只处理一个数据库。一旦它完成了对最旧数据库的清理，它会在这里调用我们，如果还有其他旧数据库存在，我们将立即通知主进程开始另一个迭代。
	 */
	if (TransactionIdFollowsOrEquals(fc_curXid, fc_xidVacLimit) &&
		IsUnderPostmaster && !InRecovery)
		SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);

	/* 如果超过包装警告点立即发出警告 */
	if (TransactionIdFollowsOrEquals(fc_curXid, fc_xidWarnLimit) && !InRecovery)
	{
		char	   *fc_oldest_datname;

		/*
		 * 我们可以在不在事务内部时被调用，例如在
		 * StartupXLOG()期间。在这种情况下，我们不能进行数据库访问，
		 * 所以我们必须仅报告最旧数据库的OID。
		 *
		 * 注意：get_database_name也可能失败并返回NULL，
		 * 例如因为数据库刚被删除。我们仍然会警告，尽管这个警告可能现在是不必要的。
		 */
		if (IsTransactionState())
			fc_oldest_datname = get_database_name(fc_oldest_datoid);
		else
			fc_oldest_datname = NULL;

		if (fc_oldest_datname)
			ereport(WARNING,
					(errmsg("database \"%s\" must be vacuumed within %u transactions",
							fc_oldest_datname,
							fc_xidWrapLimit - fc_curXid),
					 errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
		else
			ereport(WARNING,
					(errmsg("database with OID %u must be vacuumed within %u transactions",
							fc_oldest_datoid,
							fc_xidWrapLimit - fc_curXid),
					 errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
							 "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
	}
}


/*
 * ForceTransactionIdLimitUpdate -- XID 包裹限制数据是否需要更新？
 *
 * 我们主要检查 oldestXidDB 是否有效。我们考虑的情况是该数据库被删除，
 * 或字段被 pg_resetwal 重置为零。在这两种情况下，我们都应强制重新计算
 * 包裹限制。如果 oldestXid 足够旧以强制启动
 * 自动清理或其他操作；这确保我们在额外开销产生后尽快更新我们的状态。
 */
bool ForceTransactionIdLimitUpdate(void)
{
	TransactionId fc_nextXid;
	TransactionId fc_xidVacLimit;
	TransactionId fc_oldestXid;
	Oid			fc_oldestXidDB;

	/* 锁定可能并不是非常必要，但让我们小心些 */
	LWLockAcquire(XidGenLock, LW_SHARED);
	fc_nextXid = XidFromFullTransactionId(ShmemVariableCache->nextXid);
	fc_xidVacLimit = ShmemVariableCache->xidVacLimit;
	fc_oldestXid = ShmemVariableCache->oldestXid;
	fc_oldestXidDB = ShmemVariableCache->oldestXidDB;
	LWLockRelease(XidGenLock);

	if (!TransactionIdIsNormal(fc_oldestXid))
		return true;			/* 不应该发生，但以防万一 */
	if (!TransactionIdIsValid(fc_xidVacLimit))
		return true;			/* 这个也不应该再发生了 */
	if (TransactionIdFollowsOrEquals(fc_nextXid, fc_xidVacLimit))
		return true;			/* 超过了 xidVacLimit，不要延迟更新 */
	if (!SearchSysCacheExists1(DATABASEOID, ObjectIdGetDatum(fc_oldestXidDB)))
		return true;			/* 可能会发生，见上面的评论 */
	return false;
}


/*
 * GetNewObjectId -- 分配一个新的 OID
 *
 * OID 是由集群范围计数器生成的。由于它们只有 32 位宽，
 * 因此计数器最终会环绕，因此假设它们是唯一的，除非采取措施使其唯一，
 * 否则是不明智的。因此，这个例程通常不应直接使用。
 * 唯一直接调用者应为 catalog/catalog.c 中的 GetNewOidWithIndex() 和 GetNewRelFileNode()。
 */
Oid GetNewObjectId(void)
{
	Oid			fc_result;

	/* 安全检查，我们在 HS 备用中永远不应该走这么远 */
	if (RecoveryInProgress())
		elog(ERROR, "cannot assign OIDs during recovery");

	LWLockAcquire(OidGenLock, LW_EXCLUSIVE);

	/*
	 * 检查OID计数器是否回绕。我们*必须*不返回0
	 * (InvalidOid)，在正常操作中我们不能返回低于
	 * FirstNormalObjectId的任何值，因为该范围是为initdb保留的（参见
	 * IsCatalogRelationOid()）。注意我们依赖于无符号比较。
	 *
	 * 在initdb期间，我们从FirstGenbkiObjectId开始生成OID，所以我们
	 * 只有在引导或独立模式下在该点之前才会回绕。
	 * 在正常的postmaster启动后第一次通过此例程时，
	 * 计数器将被强制提升到FirstNormalObjectId。这个机制
	 * 使得FirstGenbkiObjectId与FirstNormalObjectId之间的OID
	 * 可用于initdb期间的自动分配，同时确保它们
	 * 永远不会与用户分配的OID冲突。
	 */
	if (ShmemVariableCache->nextOid < ((Oid) FirstNormalObjectId))
	{
		if (IsPostmasterEnvironment)
		{
			/* 回绕，或正常模式下的首次post-initdb分配 */
			ShmemVariableCache->nextOid = FirstNormalObjectId;
			ShmemVariableCache->oidCount = 0;
		}
		else
		{
			/* 我们可能正在引导，因此不强制执行整个范围 */
			if (ShmemVariableCache->nextOid < ((Oid) FirstGenbkiObjectId))
			{
				/* 独立模式下的回绕（不太可能但可能） */
				ShmemVariableCache->nextOid = FirstNormalObjectId;
				ShmemVariableCache->oidCount = 0;
			}
		}
	}

	/* 如果我们用完了可用的OID日志，则必须记录更多 */
	if (ShmemVariableCache->oidCount == 0)
	{
		XLogPutNextOid(ShmemVariableCache->nextOid + VAR_OID_PREFETCH);
		ShmemVariableCache->oidCount = VAR_OID_PREFETCH;
	}

	fc_result = ShmemVariableCache->nextOid;

	(ShmemVariableCache->nextOid)++;
	(ShmemVariableCache->oidCount)--;

	LWLockRelease(OidGenLock);

	return fc_result;
}

/*
 * SetNextObjectId
 *
 * 这只能在initdb期间调用；它将OID计数器
 * 推进到指定值。
 */
static void fc_SetNextObjectId(Oid fc_nextOid)
{
	/* 安全检查，这仅在initdb期间允许 */
	if (IsPostmasterEnvironment)
		elog(ERROR, "cannot advance OID counter anymore");

	/* 因此，获取锁只是形式上；但还是这样做 */
	LWLockAcquire(OidGenLock, LW_EXCLUSIVE);

	if (ShmemVariableCache->nextOid > fc_nextOid)
		elog(ERROR, "too late to advance OID counter to %u, it is now %u",
			 fc_nextOid, ShmemVariableCache->nextOid);

	ShmemVariableCache->nextOid = fc_nextOid;
	ShmemVariableCache->oidCount = 0;

	LWLockRelease(OidGenLock);
}

/*
 * StopGeneratingPinnedObjectIds
 *
 * 这在initdb期间调用一次，以强制OID计数器上升到
 * FirstUnpinnedObjectId。这支持让initdb的引导后
 * 处理在早期创建一些固定对象。一旦完成，
 * 它通过pg_stop_making_pinned_objects()调用此函数，以便
 * 它创建的剩余对象将被视为未固定。
 */
void StopGeneratingPinnedObjectIds(void)
{
	fc_SetNextObjectId(FirstUnpinnedObjectId);
}


#ifdef USE_ASSERT_CHECKING

/*
 * 断言xid在[oldestXid, nextXid]之间，这是我们
 * 期望来自表等的XID所处的范围。
 *
 * 由于ShmemVariableCache->oldestXid可能在此调用后立即更改而没有
 * 进一步的预防措施，并且回绕的xid可能再次落在
 * 有效范围内，因此此断言只能检测到某些东西是绝对
 * 错误的，但不能确立正确性。
 *
 * 这故意不公开返回值，以避免引入依赖于返回值的代码。
 */
void AssertTransactionIdInAllowableRange(TransactionId fc_xid)
{
	TransactionId fc_oldest_xid;
	TransactionId fc_next_xid;

	Assert(TransactionIdIsValid(fc_xid));

	/* 我们可能会看到引导 / 冻结 */
	if (!TransactionIdIsNormal(fc_xid))
		return;

	/*
	 * 我们无法获取 XidGenLock，因为这可能是在已经持有 XidGenLock 的情况下调用的
	 * （或者是其他不允许嵌套 XidGenLock 的锁）。不过，这对我们的目的来说是可以的，
	 * 因为我们已经依赖于 32 位读取是原子的。虽然 nextXid 是 64 位，但我们只查看
	 * 低 32 位，因此偏斜的读取不会造成伤害。
	 *
	 * 通过没有锁地访问它们并不会增加超出 [oldest, next] 的危险。
	 * xid 必须在原始会话中通过 GetNewTransactionId() 创建，
	 * 而那里的锁与下面的内存屏障配对。然而，我们确实接受 xid <=
	 * next_xid，而不仅仅是 <，因为 xid 可能来自 procarray，
	 * 在我们看到更新后的 nextXid 值之前。
	 */
	pg_memory_barrier();
	fc_oldest_xid = ShmemVariableCache->oldestXid;
	fc_next_xid = XidFromFullTransactionId(ShmemVariableCache->nextXid);

	Assert(TransactionIdFollowsOrEquals(fc_xid, fc_oldest_xid) ||
		   TransactionIdPrecedesOrEquals(fc_xid, fc_next_xid));
}
#endif
